package org.xsk.mall.api;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.xsk.mall.api.model.MethodModel;
import org.xsk.mall.api.model.ServiceModel;
import org.xsk.mall.api.util.CommandResult;
import org.xsk.mall.api.util.CommandUtil;

import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * description: protobuf api generate
 * <p>
 * date: 2021/7/14 13:03
 * author: x-one
 */
@Slf4j
public class ProtoGenerate {
    private static String execSuffix = "";
    private static File protocExec;
    private static String apiPath;
    private static final String newRow = "\n";

    private static String packagePattern = "(^option\\s+java_package\\s*=\\s*\")(\\w|.+)(\"\\s*;)";
    private static String classPattern = "(^option\\s+java_outer_classname\\s*=\\s\")(\\w+)(\"\\s*;)";
    private static String javaMultipleFilePattern = "(^option\\s+java_multiple_files\\s*=\\s*)(\\w+)(\\s*;)";
    private static String serviceListPattern = "(^service\\s*)(\\w+)(\\s*[{]?\\s*$)";
    private static String descPattern = "(^\\s*//\\s*)(.+)(\\s*$)";
    private static String methodPattern = "(^\\s*rpc\\s+)(\\w+)(\\s*\\()(\\w|.+)(\\)\\s+returns\\s*\\()(\\w|.+)(\\)\\s*;\\s*$)";

    public static void main(String[] args) {
        init();

        String protoDir = apiPath + "proto" + File.separator;
        String[] protoPaths = new String[]{"oms","pms","sms","ums","demo","common"};

        for (String protoPath : protoPaths){
            String workingFolder = protoDir + protoPath + File.separator;
            String javaFolder = apiPath + "java" + File.separator ;

            log.info("{}" , "**************************************************");
            log.info(" Generating protocol buffer files for {} ." , protoPath);
            log.info("{}" , "**************************************************");

            List<File> files =
                    FileUtil.loopFiles(workingFolder, pathname -> pathname.getAbsolutePath().endsWith(".proto"));
            if (CollectionUtil.isEmpty(files)){
                continue;
            }

            String protobufArgs = "-I=" + protoDir + " --java_out=" + javaFolder;
            String command = protocExec + " " + protobufArgs + " " + String.join(" ",
                    files.stream().map(File::getAbsolutePath).collect(Collectors.toList()));

            log.info(command);

            CommandResult result = CommandUtil.exec(command);

            log.info("exec exitValue:{} , output:{} , error:{}",result.getExitValue(),result.getOutput(),result.getError());
            if (result.getExitValue() > 0) {
                log.error("error:{}",result.getError());
                break;
            }

            genInterface(protoPath,files,javaFolder);
        }
    }

    public static void genInterface(String proto , List<File> files , String javaFolder){
        String com = "org";
        String company = "xsk";
        String project = "mall";
        String api = "api";

        Pattern packagePat = Pattern.compile(packagePattern,Pattern.MULTILINE);
        Pattern classPat = Pattern.compile(classPattern,Pattern.MULTILINE);
        Pattern javaMultiplePat = Pattern.compile(javaMultipleFilePattern,Pattern.MULTILINE);
        Pattern serviceListPat = Pattern.compile(serviceListPattern);
        Pattern methodListPat = Pattern.compile(methodPattern);
        Pattern descPat = Pattern.compile(descPattern);

        for (File file : files){
            String fileName = file.getName().split("\\.")[0];
            String content = FileUtil.readUtf8String(file);

            Matcher packageMat = packagePat.matcher(content);
            String packageName = "";
            String className = "";
            if (packageMat.find()){
                packageName = packageMat.group(2);
                Matcher classMatcher = classPat.matcher(content);
                if (classMatcher.find()){
                    className = classMatcher.group(2);
                }else {
                    log.info("check java_outer_classname in {}",file.getAbsolutePath());
                }
            }

            boolean javaMultipleFile = true;
            Matcher javaMultipleMat = javaMultiplePat.matcher(content);
            if (!javaMultipleMat.find()){
                packageName = packageName + "." + className;
                javaMultipleFile = false;
            }

            List<String> lines = FileUtil.readUtf8Lines(file);
            Map<String, ServiceModel> serviceModelMap = new HashMap<>(128);
            String serviceName = "";
            for (int i = 0; i < lines.size(); i++) {
                Matcher serviceMat = serviceListPat.matcher(lines.get(i));

                if (serviceMat.matches()){
                    serviceName = serviceMat.group(2);

                    ServiceModel serviceModel = new ServiceModel();
                    serviceModel.setServiceName(serviceName);
                    serviceModel.setClassName(className);
                    serviceModel.setPackageName(packageName);
                    serviceModel.setFileName(fileName);
                    String filePath = javaFolder + File.separator + com + File.separator + company + File.separator
                            + project + File.separator + api + File.separator + proto + File.separator + fileName
                            + File.separator;
                    serviceModel.setFilePath(filePath);
                    serviceModel.setModule(proto);
                    serviceModel.setMultipartFile(javaMultipleFile);

                    if (i > 0){
                        Matcher serviceDescMat = descPat.matcher(lines.get(i - 1));
                        if (serviceDescMat.matches()){
                            serviceModel.setServiceDesc(serviceDescMat.group(2));
                        }else {
                            serviceModel.setServiceDesc(serviceName);
                        }
                    }

                    if (serviceModelMap.containsKey(serviceName)){
                        log.error("duplicate service name !!!");
                    }else {
                        serviceModelMap.put(serviceName,serviceModel);
                    }
                }else {
                    Matcher matcher = methodListPat.matcher(lines.get(i));
                    if (matcher.matches() && serviceModelMap.containsKey(serviceName)){
                        MethodModel model = new MethodModel();
                        model.setMethodName(matcher.group(2));
                        model.setRequestName(matcher.group(4));
                        model.setResponseName(matcher.group(6));
                        Matcher descMat = descPat.matcher(lines.get(i - 1));
                        if (descMat.matches()){
                            model.setMethodDesc(descMat.group(2));
                        }else {
                            model.setMethodDesc("");
                        }
                        serviceModelMap.get(serviceName).getMethodModelList().add(model);
                    }
                }
            }

            // write file
            writeFile(serviceModelMap);
        }
    }

    public static void writeFile(Map<String, ServiceModel> map){
        if (map.isEmpty()) return;

        for (Map.Entry<String,ServiceModel> serviceEntry : map.entrySet()){
            ServiceModel serviceModel = serviceEntry.getValue();
            String file ;
            if (serviceModel.isMultipartFile()){
                file = serviceModel.getFilePath() + serviceModel.getServiceName() + ".java";
            }else{
                file = serviceModel.getFilePath() + serviceModel.getClassName() + ".java";
            }

            List<String> content = new ArrayList<>();
            content.add("package " + serviceModel.getPackageName() + "." + serviceModel.getFileName() + ";");
            content.add("");

            Set<String> imports = new TreeSet<>();
            for (MethodModel method : serviceModel.getMethodModelList()){
                if (!imports.contains(method.getRequestName())){
                    imports.add("import " + serviceModel.getPackageName() + "." + method.getRequestName() + ";");
                }
                if (!imports.contains(method.getResponseName())){
                    imports.add("import " + serviceModel.getPackageName() + "." + method.getResponseName() + ";");
                }
            }

            for (String line : imports){
                content.add(line);
            }

            content.add("");

            content.add("import org.springframework.web.bind.annotation.RequestMapping;");
            content.add("import org.springframework.web.bind.annotation.RequestMethod;");
            content.add("import org.springframework.web.bind.annotation.RequestBody;");

            content.add("/**" + newRow + " * @Type " + serviceModel.getServiceName() + newRow + " * @Desc " + serviceModel.getServiceDesc() + newRow + " */");
            content.add("@RequestMapping" + "(\"/" + serviceModel.getModuleUrl() + "/" + serviceModel.getFileName() + "\")");
            content.add("public interface " + serviceModel.getServiceName() + " {" + newRow);

            for (MethodModel methodModel : serviceModel.getMethodModelList()){
                String req = methodModel.getRequestName();
                String resp = methodModel.getResponseName();

                content.add(newRow);
                content.add("    /**" + newRow + "    * " + methodModel.getMethodDesc() + newRow + "    */");
                content.add("    @RequestMapping(method = RequestMethod.POST,value = \"/"
                        + serviceModel.getServiceName() + "/" + methodModel.getMethodNameLower()
                        + "\"," + newRow +
                        "            consumes = {\"application/x-protobuf\",\"application/json\"},"
                        + newRow +
                        "            produces = {\"application/x-protobuf\",\"application/json\"})");

                content.add("    " + resp + " " + methodModel.getMethodNameLower() + "(@RequestBody(required = false) " + req
                        + " " + req.substring(0,1).toLowerCase() + req.substring(1) + ");");
            }
            content.add("}" + newRow);
            FileUtil.del(file);
            for (String line : content){
                FileUtil.appendUtf8String(line + newRow , file);
            }
        }
    }

    /**
     * 初始化apiPath
     * 根据当前操作系统选在protocExec
     */
    public static void init(){
        // API项目路径
        apiPath =
                new File("").getAbsolutePath() + File.separator + "src" + File.separator + "main" + File.separator;
        String os = System.getProperty("os.name");
        log.info("current os: {}" , os);

        String execFolderName;
        if (os.startsWith("Windows")){
            execFolderName = "protoc-3.17.3-win64";
            execSuffix = ".exe";
        }else if (os.startsWith("Linux")){
            execFolderName = "protoc-3.17.3-linux-x86_64";
        }else {
            log.info("{}","Error: Only support Windows/Linux");
            return;
        }

        String protobufExecFile = apiPath + "protoc" + File.separator + execFolderName + File.separator + "bin"
                + File.separator + "protoc" + execSuffix;
        protocExec = new File(protobufExecFile);
        log.info("protobuf actuator is : {}" , protocExec.getAbsolutePath());

    }

}
